// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef ZIRCON_TOOLS_XDC_SERVER_XDC_SERVER_H_
#define ZIRCON_TOOLS_XDC_SERVER_XDC_SERVER_H_

#include <poll.h>

#include <map>
#include <set>
#include <vector>

#include <fbl/unique_fd.h>
#include <xdc-server-utils/msg.h>

#include "usb-handler.h"

namespace xdc {

class Client {
 public:
  explicit Client(int fd) : fd_(fd) {}

  void SetStreamId(uint32_t stream_id);
  void SetConnected(bool connected);
  // Returns whether the set of client events we are polling for has changed.
  bool UpdatePollState(bool usb_writable);

  // Queues a completed read transfer to be written to the client.
  void AddCompletedRead(std::unique_ptr<UsbHandler::Transfer> transfer);
  // Writes data from completed read transfers to the client until there are
  // no transfers left, or the client is currently unavailable to accept data.
  void ProcessCompletedReads(const std::unique_ptr<UsbHandler>& usb_handler);
  // Writes data read from the client to the usb handler.
  zx_status_t ProcessWrites(const std::unique_ptr<UsbHandler>& usb_handler);
  // Returns any unused transfers to the usb handler.
  void ReturnTransfers(const std::unique_ptr<UsbHandler>& usb_handler);

  int fd() const { return fd_.get(); }
  // Returns the set of client events we should poll for.
  short events() const { return events_; }
  bool registered() const { return registered_; }
  uint32_t stream_id() const { return stream_id_; }
  bool connected() const { return connected_; }
  // Returns true if we have read data from the client not yet sent to the usb handler.
  bool has_write_data() const { return pending_write_ && pending_write_->request_length() > 0; }

 private:
  fbl::unique_fd fd_;

  short events_ = 0;

  // Whether the client has registered a stream id.
  bool registered_ = false;
  uint32_t stream_id_ = 0;
  // True if the client has registered a stream id,
  // and that stream id is also registered on the xdc device side.
  bool connected_ = false;

  std::vector<std::unique_ptr<UsbHandler::Transfer>> completed_reads_;
  // Data read from the client, to be sent to the usb handler.
  std::unique_ptr<UsbHandler::Transfer> pending_write_;
};

class XdcServer {
  // This is required by the XdcServer constructor, to stop clients calling it directly.
  struct ConstructorTag {
    explicit ConstructorTag() = default;
  };

 public:
  // Create should be called instead. This is public for make_unique.
  XdcServer(ConstructorTag tag) {}

  static std::unique_ptr<XdcServer> Create();
  void Run();

 private:
  bool Init();

  void UpdateClientPollEvents();

  // Updates poll_fds_ with any newly added or removed usb handler fds.
  void UpdateUsbHandlerFds();

  // Processes new client connections on the server socket.
  void ClientConnect();

  // Returns whether registration succeeded.
  bool RegisterStream(std::shared_ptr<Client> client);

  // Returns the client registered to the given stream id, or nullptr if none was found.
  std::shared_ptr<Client> GetClient(uint32_t stream_id);

  void UsbReadComplete(std::unique_ptr<UsbHandler::Transfer> transfer);
  // Parses the control message from the given transfer buffer.
  void HandleCtrlMsg(unsigned char* transfer_buf, int transfer_len);

  // Sends a control message the to the xdc device with whether a stream has gone on / offline.
  // If the message cannot currently be sent, it is queued to be retried later.
  void NotifyStreamState(uint32_t stream_id, bool online);
  bool SendCtrlMsg(xdc_msg_t& msg);
  void SendQueuedCtrlMsgs();

  std::unique_ptr<UsbHandler> usb_handler_;

  // Server socket we receive client connections on.
  fbl::unique_fd socket_fd_;
  // File lock acquired to ensure we don't unlink the socket of a running xdc server instance.
  fbl::unique_fd socket_lock_fd_;

  // Maps from client socket file descriptor to client.
  std::map<int, std::shared_ptr<Client>> clients_;

  // File descriptors we are currently polling on.
  std::vector<pollfd> poll_fds_;

  // Stream ids registered on the xdc device side.
  std::set<uint32_t> dev_stream_ids_;

  std::vector<xdc_msg_t> queued_ctrl_msgs_;

  xdc_packet_state_t read_packet_state_;
};

}  // namespace xdc

#endif  // ZIRCON_TOOLS_XDC_SERVER_XDC_SERVER_H_
